package freespiritsocial.io.user.ui.adapter

import android.text.SpannableString
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.util.Base64
import android.util.SparseIntArray
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.chad.library.adapter.base.BaseDelegateMultiAdapter
import com.chad.library.adapter.base.delegate.BaseMultiTypeDelegate
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.luck.picture.lib.entity.LocalMedia
import com.sendbird.android.*
import com.sendbird.android.BaseChannel.GetMessagesHandler
import freespiritsocial.io.user.R
import freespiritsocial.io.user.event.EventViewModel
import freespiritsocial.io.user.ext.circleImageUrl
import freespiritsocial.io.user.ext.loadImageWithCorner
import freespiritsocial.io.user.ext.setAdapterAnimation
import freespiritsocial.io.user.ui.fragment.message.SharePostListDialogFragment
import freespiritsocial.io.user.util.DateUtils
import freespiritsocial.io.user.util.FileUtils
import freespiritsocial.io.user.util.TextUtils
import freespiritsocial.io.user.weight.MessageStatusView
import java.io.File
import java.io.IOException
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern

class ChatAdapter(val eventViewModel: EventViewModel,data: ArrayList<BaseMessage> = arrayListOf()):
    BaseDelegateMultiAdapter<BaseMessage,BaseViewHolder>(data) {
    companion object {
        private const val VIEW_TYPE_USER_MESSAGE_ME = 10
        private const val VIEW_TYPE_USER_MESSAGE_OTHER = 11
        private const val VIEW_TYPE_FILE_MESSAGE_IMAGE_ME = 22
        private const val VIEW_TYPE_FILE_MESSAGE_IMAGE_OTHER = 23
        private const val VIEW_TYPE_FILE_MESSAGE_VIDEO_ME = 24
        private const val VIEW_TYPE_FILE_MESSAGE_VIDEO_OTHER = 25
        private const val VIEW_TYPE_ADMIN_MESSAGE = 30
        private val layouts = SparseIntArray().apply {
            append(VIEW_TYPE_USER_MESSAGE_ME,R.layout.list_item_group_chat_user_me)
            append(VIEW_TYPE_USER_MESSAGE_OTHER,R.layout.list_item_group_chat_user_other)
            append(VIEW_TYPE_FILE_MESSAGE_IMAGE_ME,R.layout.list_item_group_chat_file_image_me)
            append(VIEW_TYPE_FILE_MESSAGE_IMAGE_OTHER,R.layout.list_item_group_chat_file_image_other)
            append(VIEW_TYPE_FILE_MESSAGE_VIDEO_ME,R.layout.list_item_group_chat_file_video_me)
            append(VIEW_TYPE_FILE_MESSAGE_VIDEO_OTHER,R.layout.list_item_group_chat_file_video_other)
        }
    }

    var urlClick: (String) -> Unit = { }

    var channel: GroupChannel? = null

    private var mIsMessageListLoading = false


    private val mTempFileMessagePathTable = Hashtable<String,LocalMedia>()

    init {
        setAdapterAnimation(3)
        addChildClickViewIds(R.id.message_status_group_chat)
        setMultiTypeDelegate(
            object: BaseMultiTypeDelegate<BaseMessage>(layouts) {
                override fun getItemType(data: List<BaseMessage>,position: Int): Int {
                    when (val message: BaseMessage = data[position]) {
                        is UserMessage -> {
                            return if (message.sender.userId == SendBird.getCurrentUser().userId) {
                                VIEW_TYPE_USER_MESSAGE_ME
                            } else {
                                VIEW_TYPE_USER_MESSAGE_OTHER
                            }
                        }
                        is FileMessage -> {
                            return if (message.type.toLowerCase(Locale.getDefault()).startsWith("image")) {
                                if (message.sender.userId == SendBird.getCurrentUser().userId) {
                                    VIEW_TYPE_FILE_MESSAGE_IMAGE_ME
                                } else {
                                    VIEW_TYPE_FILE_MESSAGE_IMAGE_OTHER
                                }
                            } else {
                                if (message.sender.userId == SendBird.getCurrentUser().userId) {
                                    VIEW_TYPE_FILE_MESSAGE_VIDEO_ME
                                } else {
                                    VIEW_TYPE_FILE_MESSAGE_VIDEO_OTHER
                                }
                            }
                        }
                        /*is AdminMessage -> {
                            return VIEW_TYPE_ADMIN_MESSAGE
                        }*/
                        else -> return -1
                    }
                }
            }
        )
    }


    override fun convert(holder: BaseViewHolder,message: BaseMessage) {
        val position = holder.absoluteAdapterPosition
        var isContinuous = false
        var isNewDay = false
        val isTempMessage = isTempMessage(message)
        val tempFileMessagePath: String? = getTempFileMessageUri(message)?.compressPath

        if (position < data.size - 1) {
            val prevMessage: BaseMessage = data[position + 1]

            if (!DateUtils.hasSameDate(message.createdAt,prevMessage.createdAt)) {
                isNewDay = true
                isContinuous = false
            } else {
                isContinuous = isContinuous(message,prevMessage)
            }
        } else if (position == data.size - 1) {
            isNewDay = true
        }

        when (holder.itemViewType) {
            VIEW_TYPE_USER_MESSAGE_ME -> {
                bindMessageMe(holder,message as UserMessage,isNewDay)
            }
            VIEW_TYPE_USER_MESSAGE_OTHER -> {
                bindMessageOther(holder,message as UserMessage,isContinuous,isNewDay)
            }
            VIEW_TYPE_FILE_MESSAGE_IMAGE_ME -> {
                bindImageMessageMe(holder,message as FileMessage,isNewDay,isTempMessage,tempFileMessagePath)
            }
            VIEW_TYPE_FILE_MESSAGE_IMAGE_OTHER -> {
                bindImageMessageOther(holder,message as FileMessage,isNewDay,isContinuous)
            }
            VIEW_TYPE_FILE_MESSAGE_VIDEO_ME -> {
                bindVideoMessageMe(holder,message as FileMessage,isNewDay,isTempMessage,tempFileMessagePath)
            }
            VIEW_TYPE_FILE_MESSAGE_VIDEO_OTHER -> {
                bindVideoMessageOther(holder,message as FileMessage,isNewDay,isContinuous)
            }
            else -> {
            }
        }
    }

    fun markAllMessagesAsRead() {
        channel?.markAsRead {
            SendBird.getTotalUnreadChannelCount { i,sendBirdException ->
                eventViewModel.hasUnReadMessage.postValue(i != 0)
            }
        }
        notifyDataSetChanged()
    }

    /**
     * Replaces current message list with new list.
     * Should be used only on initial load or refresh.
     */
    fun loadLatestMessages(limit: Int,handler: GetMessagesHandler?) {
        if (channel == null) {
            return
        }
        if (mIsMessageListLoading) {
            return
        }
        mIsMessageListLoading = true
        channel?.getMessagesByTimestamp(
            Long.MAX_VALUE,MessageListParams().apply {
                isInclusive = true
                previousResultSize = limit
                setReverse(true)
                messageType = BaseChannel.MessageTypeFilter.ALL
            },GetMessagesHandler { list,e ->
                handler?.onResult(list,e)
                mIsMessageListLoading = false
                if (e != null) {
                    e.printStackTrace()
                    return@GetMessagesHandler
                }
                if (list.size <= 0) {
                    return@GetMessagesHandler
                }
                for (message in data) {
                    if (isTempMessage(message) || isFailedMessage(message)) {
                        list.add(0,message)
                    }
                }
                setList(list)
            })
    }


    /**
     * 我发的消息
     * @param holder BaseViewHolder
     * @param message UserMessage
     * @param isNewDay Boolean
     */
    private fun bindMessageMe(
        holder: BaseViewHolder,
        message: UserMessage,
        isNewDay: Boolean
    ) {

        bindTime(holder,message,isNewDay)

        setContentHttpPattern(holder.getView(R.id.text_group_chat_message),message.message)

        val messageStatusView = holder.getView<MessageStatusView>(R.id.message_status_group_chat)
        messageStatusView.drawMessageStatus(message)
    }

    /**
     * 我发的图片
     */
    private fun bindImageMessageMe(
        holder: BaseViewHolder,
        message: FileMessage,
        isNewDay: Boolean,
        isTempMessage: Boolean,
        tempFileMessageUri: String?,
    ) {

        bindTime(holder,message,isNewDay)

        val messageStatusView = holder.getView<MessageStatusView>(R.id.message_status_group_chat)
        messageStatusView.drawMessageStatus(message)

        //图片加载
        val thumbnail = holder.getView<ImageView>(R.id.image_group_chat_file_thumbnail)
        if (isTempMessage && tempFileMessageUri != null) {
            thumbnail.loadImageWithCorner(tempFileMessageUri,11,R.color.divider)
        } else {
            bindImageMessage(message,thumbnail)
        }
    }

    /**
     * 我发的视频
     */
    private fun bindVideoMessageMe(
        holder: BaseViewHolder,
        message: FileMessage,
        isNewDay: Boolean,
        isTempMessage: Boolean,
        tempFileMessageUri: String?,
    ) {
        bindTime(holder,message,isNewDay)
        val messageStatusView = holder.getView<MessageStatusView>(R.id.message_status_group_chat)
        messageStatusView.drawMessageStatus(message)
        //图片加载
        val thumbnail = holder.getView<ImageView>(R.id.image_group_chat_file_thumbnail)
        if (isTempMessage && tempFileMessageUri != null) {
            thumbnail.loadImageWithCorner(tempFileMessageUri,11,R.color.divider,FitCenter())
        } else {
            bindImageMessage(message,thumbnail)
        }
    }

    /**
     * 对方发的消息
     * @param holder BaseViewHolder
     * @param message UserMessage
     * @param isNewDay Boolean
     */
    private fun bindMessageOther(
        holder: BaseViewHolder,
        message: UserMessage,
        isContinuous: Boolean,
        isNewDay: Boolean
    ) {
        //时间
        bindTime(holder,message,isNewDay)
        //头像
        bindOtherProfileImage(holder,message,isContinuous)
        //消息
        setContentHttpPattern(holder.getView(R.id.text_group_chat_message),message.message)
    }

    /**
     * 对方发的图片
     */
    private fun bindImageMessageOther(
        holder: BaseViewHolder,
        message: FileMessage,
        isNewDay: Boolean,
        isContinuous: Boolean,
    ) {
        bindTime(holder,message,isNewDay)
        bindOtherProfileImage(holder,message,isContinuous)

        val thumbnail = holder.getView<ImageView>(R.id.image_group_chat_file_thumbnail)
        bindImageMessage(message,thumbnail)

    }

    /**
     * 对方发送的视频
     */
    private fun bindVideoMessageOther(
        holder: BaseViewHolder,
        message: FileMessage,
        isNewDay: Boolean,
        isContinuous: Boolean,
    ) {
        bindTime(holder,message,isNewDay)
        bindOtherProfileImage(holder,message,isContinuous)

        val thumbnail = holder.getView<ImageView>(R.id.image_group_chat_file_thumbnail)
        bindImageMessage(message,thumbnail)

    }

    //===============UI复用========================

    /**
     * 设置消息时间 日期
     * @param holder BaseViewHolder
     * @param message BaseMessage
     * @param isNewDay Boolean
     */
    private fun bindTime(
        holder: BaseViewHolder,
        message: BaseMessage,
        isNewDay: Boolean
    ) {
        val dateText = holder.getView<TextView>(R.id.text_group_chat_date)
        holder.setText(R.id.text_group_chat_time,DateUtils.formatTime(message.createdAt))
        dateText.isVisible = isNewDay
        dateText.text = DateUtils.formatDate(message.createdAt)
    }

    /**
     * 设置图片信息
     * @param message FileMessage
     * @param thumbnail ImageView
     */
    private fun bindImageMessage(message: FileMessage,thumbnail: ImageView) {
        val thumbnails = message.data
        thumbnail.loadImageWithCorner(if (thumbnails.isNullOrEmpty()) message.url else thumbnails,11)
    }

    /**
     * 设置对方头像
     * @param holder BaseViewHolder
     * @param message FileMessage
     * @param isContinuous Boolean
     */
    private fun bindOtherProfileImage(
        holder: BaseViewHolder,
        message: BaseMessage,
        isContinuous: Boolean
    ) {
        val profileImage = holder.getView<ImageView>(R.id.image_group_chat_profile)
        profileImage.circleImageUrl(message.sender.profileUrl)
        profileImage.isInvisible = isContinuous
    }


    /**
     * 检查当前消息是否由发送前一条消息的同一个人发送。
     * 这样做是为了在不必要时不必显示冗余 UI，例如发件人昵称和个人资料图片。
     */
    private fun isContinuous(currentMsg: BaseMessage?,precedingMsg: BaseMessage?): Boolean {
        if (currentMsg == null || precedingMsg == null) {
            return false
        }
        if (currentMsg is AdminMessage && precedingMsg is AdminMessage) {
            return true
        }
        var currentUser: User? = null
        var precedingUser: User? = null
        if (currentMsg is UserMessage) {
            currentUser = currentMsg.sender
        } else if (currentMsg is FileMessage) {
            currentUser = currentMsg.sender
        }
        if (precedingMsg is UserMessage) {
            precedingUser = precedingMsg.sender
        } else if (precedingMsg is FileMessage) {
            precedingUser = precedingMsg.sender
        }

        return (!(currentUser == null || precedingUser == null)
                && currentUser.userId == precedingUser.userId)
    }

    /**
     * 设置url识别
     * @param view TextView
     * @param string String
     */
    private fun setContentHttpPattern(view: TextView,string: String) {
        val sp = SpannableString(string)
        val urlPattern =
            "((http[s]?|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
        val pattern: Pattern = Pattern.compile(urlPattern,Pattern.CASE_INSENSITIVE)
        val m: Matcher = pattern.matcher(string)
        var startPoint = 0
        while (m.find(startPoint)) {
            val endPoint: Int = m.end()
            val hit: String = m.group()
            val clickSpan: ClickableSpan = UrLClickSpan(hit)
            sp.setSpan(clickSpan,endPoint - hit.length,endPoint,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) //用Span替换对应长度的url
            sp.setSpan(ForegroundColorSpan(context.getColor(R.color.ff09c6b7)),endPoint - hit.length,endPoint,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            startPoint = endPoint
        }
        view.text = sp
        view.movementMethod = LinkMovementMethod.getInstance()
    }

    /**
     * 缓存文件的消息详情
     * 发送时调用
     */
    fun addTempFileMessageInfo(message: FileMessage,uri: LocalMedia?) {
        mTempFileMessagePathTable[message.requestId] = uri
    }

    fun addFirst(message: BaseMessage) {
        addData(0,message)
    }

    fun isTempMessage(message: BaseMessage): Boolean {
        return message.messageId == 0L
    }

    fun isFailedMessage(message: BaseMessage): Boolean {
        return if (!isTempMessage(message)) {
            false
        } else message.sendingStatus == BaseMessage.SendingStatus.FAILED
    }

    fun getTempFileMessageUri(message: BaseMessage?): LocalMedia? {
        if (!isTempMessage(message!!)) {
            return null
        }
        return if (message !is FileMessage) {
            null
        } else mTempFileMessagePathTable[message.requestId]
    }

    fun removeFailedMessage(message: BaseMessage) {
        mTempFileMessagePathTable.remove(message.requestId)
        remove(message)
    }

    /**
     * 发送消息完成后刷新消息数据
     */
    fun markMessageSent(message: BaseMessage) {
        var msg: BaseMessage
        for (i in data.indices) {
            msg = data[i]
            if (message is UserMessage && msg is UserMessage) {
                if (msg.requestId == message.requestId) {
                    setData(i,message)
                    return
                }
            } else if (message is FileMessage && msg is FileMessage) {
                if (msg.requestId == message.requestId) {
                    mTempFileMessagePathTable.remove(message.requestId)
                    setData(i,message)
                    return
                }
            }
        }
    }

    fun update(message: BaseMessage) {
        findIndexById(message.messageId)?.let { index ->
            setData(index,message)
        }
    }

    fun delete(msgId: Long) {
        findIndexById(msgId)?.let {
            removeAt(it)
        }
    }

    private fun findIndexById(id: Long) = data.indexOfFirst { it.messageId == id }.takeIf { it >= 0 }

    /**
     * 加载本地聊天记录
     * @param channelUrl String
     */
    fun load(channelUrl: String) {
        try {
            val appDir = File(context.cacheDir,SendBird.getApplicationId())
            appDir.mkdirs()
            val dataFile = File(appDir,TextUtils.generateMD5(SendBird.getCurrentUser().userId + channelUrl).toString() + ".data")
            val content: String = FileUtils.loadFromFile(dataFile)
            val dataArray = content.split("\n".toRegex()).toTypedArray()
            channel = GroupChannel.buildFromSerializedData(Base64.decode(dataArray[0],Base64.DEFAULT or Base64.NO_WRAP)) as GroupChannel

            // Reset message list, then add cached messages.
            data.clear()
            for (i in 1 until dataArray.size) {
                data.add(BaseMessage.buildFromSerializedData(Base64.decode(dataArray[i],Base64.DEFAULT or Base64.NO_WRAP)))
            }
            notifyDataSetChanged()
        } catch (e: Exception) {
            // Nothing to load.
        }
    }

    /**
     * 保存本地聊天记录
     */
    fun save() {
        try {
            val sb = StringBuilder()
            if (channel != null) {
                // Convert current data into string.
                sb.append(Base64.encodeToString(channel!!.serialize(),Base64.DEFAULT or Base64.NO_WRAP))
                var message: BaseMessage? = null
                for (i in 0 until data.size.coerceAtMost(100)) {
                    message = data[i]
                    if (!isTempMessage(message)) {
                        sb.append("\n")
                        sb.append(Base64.encodeToString(message.serialize(),Base64.DEFAULT or Base64.NO_WRAP))
                    }
                }
                val data = sb.toString()
                val md5: String = TextUtils.generateMD5(data)

                // Save the data into file.
                val appDir: File = File(context.cacheDir,SendBird.getApplicationId())
                appDir.mkdirs()
                val hashFile = File(appDir,TextUtils.generateMD5(SendBird.getCurrentUser().userId + channel!!.getUrl()).toString() + ".hash")
                val dataFile = File(appDir,TextUtils.generateMD5(SendBird.getCurrentUser().userId + channel!!.getUrl()).toString() + ".data")
                try {
                    val content: String = FileUtils.loadFromFile(hashFile)
                    // If data has not been changed, do not save.
                    if (md5 == content) {
                        return
                    }
                } catch (e: IOException) {
                    // File not found. Save the data.
                }
                FileUtils.saveToFile(dataFile,data)
                FileUtils.saveToFile(hashFile,md5)
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun markMessageFailed(message: BaseMessage) {
        data.forEachIndexed { index,baseMessage ->
            if (baseMessage.requestId == message.requestId) {
                setData(index,message)
                return@forEachIndexed
            }
        }
    }

    inner class UrLClickSpan(val hit: String): ClickableSpan() {
        override fun onClick(widget: View) {
            if (hit.contains(SharePostListDialogFragment.APP_SHARE_HOST).not()) return
            val id = hit.substring(hit.indexOf(SharePostListDialogFragment.APP_SHARE_HOST),hit.indexOfLast { it == '?' })
                .replace(SharePostListDialogFragment.APP_SHARE_HOST,"")
            urlClick(id)
        }
    }
}
